/*
This software is distributed under the Simplified BSD License:

Copyright (c) 2008, Chris Venter <chris.venter@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, 
    	this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice, 
    	this list of conditions and the following disclaimer in the documentation 
    	and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 
DAMAGE.
*/
#ifndef GENERIK_OPTIONS_H
#define GENERIK_OPTIONS_H

/** \file options.h
Commandline arguments processing skeleton class
*/

#include <unistd.h>
#include <map>
#include "macros.h"
#include "tracing.h"

#include <vector>
#include <stdexcept>
	
#define GENERIK_OPTIONS_H_TRACER REM_GENERIK_TRACER;
//#define GENERIK_OPTIONS_H_TRACER GENERIK_TRACER;

namespace generik
{
	template <typename TYPEOUT, typename TYPEIN>
	void convert(const TYPEIN& valuein, TYPEOUT& valueout)
	{
		GENERIK_INLINE("CONVERT: using std::stringstream");
		std::stringstream ss;
		ss<<valuein;
		ss>>valueout;
	}

	template <typename TYPE>
	void convert(const TYPE& valuein, TYPE& valueout)
	{
		GENERIK_INLINE("CONVERT: using operator=");
		valueout = valuein;
	}
	
	template <>
	void convert(const std::string& valuein, std::string& valueout)
		{
		GENERIK_INLINE("CONVERT: std::string::operator=");
		valueout = valuein;
		}
	
	template <>
	void convert(const std::string& valuein, int& valueout)
		{
		GENERIK_INLINE("CONVERT: using atoi");
		valueout = atoi(valuein.c_str());
	}
	
	template <>
	void convert(const std::string& valuein, double& valueout)
			{
		GENERIK_INLINE("CONVERT: using atof");
		valueout = atof(valuein.c_str());
			}
	
	struct val
		{
		virtual ~val() {}
		virtual void set(const std::string& value) = 0;
		virtual val* copy() = 0;
	};
	
	template <typename TYPE>
	struct val_
	: val
			{
		TYPE& _value;
		
		val_(TYPE& value)
		: _value(value)
		{
			GENERIK_OPTIONS_H_TRACER;
		}

		~val_()
		{
			GENERIK_OPTIONS_H_TRACER;
			}
		
		void set(const std::string& value)
			{
			convert(value, _value);
			}
		
		val* copy()
		{
			return new val_<TYPE>(_value);
		}
	};

	struct arg
	{
		std::string _opt, _brief, _detail;
		val* _value;
		bool _required;
		
		template <typename TYPE>
		arg(const std::string& opt, const std::string& brief, const std::string& detail, TYPE& value, bool required)
		: _opt(opt), _brief(brief), _detail(detail)
		, _value(new val_<TYPE>(value))
		, _required(required)
		{
			GENERIK_OPTIONS_H_TRACER;
		}
		
		arg(const arg& a)
		: _opt(a._opt), _brief(a._brief), _detail(a._detail)
		, _value(a._value->copy())
		, _required(a._required)
		{
			GENERIK_OPTIONS_H_TRACER;
		}
		
		char flag()
		{
			return _opt[0];
		}
		
		bool set(const char* value)
		{
			GENERIK_OPTIONS_H_TRACER;
			if (value)
		{
				//std::cout<<"setting value to "<<value<<std::endl;
			_value->set(value);
				//std::cout<<"done"<<std::endl;
			}
			else
			if (_required)
			{
				GENERIK_ERROR("missing argument for "<<brief());
				return false;
		}
			else
			{
				//std::cout<<"setting value to \"1\""<<std::endl;
				_value->set("1");
				//std::cout<<"done!"<<std::endl;
			}
			return true;
		}
		
		std::string brief()
		{
			std::stringstream ss;
			if (!_required)
			{
				ss<<"[";
			}
			if (!_opt.empty())
			{
				ss<<"-"<<(char)_opt[0]<<" ";
			}
			ss<<"<"<<_brief<<">";
			if (!_required)
			{
				ss<<"]";
			}
			return ss.str();
		}
		
		~arg()
		{
			GENERIK_OPTIONS_H_TRACER;
			delete _value;
		}
	};

	struct arglist
		{
		std::vector<arg*> args;
		
		~arglist()
		{
			GENERIK_OPTIONS_H_TRACER;
			GENERIK_FOR_EACH(i, args)
			{
				delete *i;
			}
		}
		
		unsigned int find_(char flag, unsigned int offset = 0)
			{
			for (unsigned int i = offset; i < args.size(); ++i)
				{
				char f = args[i]->flag();
				if (f == flag)
				{
					return i;
				}
			}
			return -1;
		}
		
		arg* find(char flag, unsigned int& offset)
		{
			unsigned int i = find_(flag, offset);
			if (i != -1)
			{
				offset = i + 1;
				arg* v = args[i];
				return v;
			}
			return 0;
		}
		
		arg* find(char flag)
		{
			unsigned int i = find_(flag, 0);
			if (i != -1)
			{
				arg* v = args[i];
				return v;
			}
			return 0;
		}
		
		template <typename TYPE>
		void add(const std::string& opt, const std::string& brief, const std::string& detail, TYPE& value, bool required)
		{
			args.push_back(new arg(opt, brief, detail, value, required));
		}
		
		std::string getoptstr()
		{
			std::string result = ":h";
			GENERIK_FOR_EACH(i, args)
			{
				result += (*i)->_opt;
			}
			return result;
		}
		
		std::string usage(const std::string& progname)
		{
			std::stringstream result;
			result<<"Usage: "<<progname<<" ";
			GENERIK_FOR_EACH(i, args)
			{
				result<<(*i)->brief()<<" ";
			}
			result<<"\nwhere\n";
			GENERIK_FOR_EACH(i, args)
			{
				result<<"\t"<<(*i)->brief()<<" "<<(*i)->_detail<<"\n";
			}
			return result.str();
		}
	};
	
	struct options
	{
		arglist _list;
		int _argc;
		char** _argv;
		
		int _oldopterr;
		
		options()
		: _argc(-1)
		, _argv(0)
		{
			GENERIK_OPTIONS_H_TRACER;
			_oldopterr = opterr; opterr = 0;
		}
		
		~options()
		{
			GENERIK_OPTIONS_H_TRACER;
			opterr = _oldopterr;
		}

		/** Shows usage information.
		 This method shows usage information on the commandline, and takes
		 an optional quit parameter (true, by default).
		 It has to be implemented by derived classes, and can make use of the provided usage_() method.
		*/
		std::string usage()
		{
			return _list.usage(_argv[0]);
		}
		
		template <typename TYPE>
		void add(const std::string& opt, const std::string& brief, const std::string& detail, TYPE& value, bool required = false)
		{
			_list.add(opt, brief, detail, value, required);
		}
		
		virtual void error()
		{
			throw std::runtime_error("could not complete argument parsing");
			//exit(-1);
		}
		
		virtual void process_help()
		{
			GENERIK_OPTIONS_H_TRACER;
			GENERIK_ERROR(usage());
			error();
		}
		
		virtual void process_unknown(char arg)
		{
			GENERIK_OPTIONS_H_TRACER;
			GENERIK_ERROR("unknown argument ["<<arg<<"]");
			GENERIK_ERROR(usage());
			error();
		}
		
		virtual void process_missing(char opt)
		{
			GENERIK_OPTIONS_H_TRACER;
			arg* found = _list.find(opt);
			GENERIK_ERROR("missing argument for "<<found->brief());
			GENERIK_ERROR(usage());
			error();
		}
		
		virtual void process_argument(char opt)
		{
			GENERIK_OPTIONS_H_TRACER;
			arg* found = _list.find(opt);
			if (found)
			{
				if (!found->set(optarg))
				{
					GENERIK_ERROR("error parsing argument ["<<(char)opt<<"]");
					GENERIK_ERROR(usage());
					error();
				}
			}
			else
			{
				GENERIK_ERROR("unknown argument ["<<(char)opt<<"]");
				GENERIK_ERROR(usage());
				error();
			}
		}
		
		virtual void process_post()
		{
			GENERIK_OPTIONS_H_TRACER;
			if (!post())
			{
				GENERIK_ERROR("could not post-process the arguments");
				GENERIK_ERROR(usage());
				error();
			}
		}
		
		virtual void process_extra_arg(arg* found, const char* arg)
		{
			GENERIK_OPTIONS_H_TRACER;
			if (!found->set(arg))
			{
				GENERIK_ERROR("error parsing extra argument");
				GENERIK_ERROR(usage());
				error();
			}
		}
		
		virtual void process_extra_arg(const char* arg)
		{
			GENERIK_OPTIONS_H_TRACER;
			GENERIK_ERROR("too many extra arguments");
			GENERIK_ERROR(usage());
			error();
		}

		/** Parses a flag.
		 This method has to interpret the flag as passed to it by the run() method.
		 It should be checked for validity (ie the default return should be false).
		 If the flag takes additional data, this can be accessed using the optarg
		 variable, as provided by the getopt package.
		 It has to be implemented by derived classes.
		 */
		//virtual bool parse(int opt) = 0;
		virtual void run(int& argc, char**& argv)
		{
			GENERIK_OPTIONS_H_TRACER;
			_argc = argc;
			_argv = argv;
			GENERIK_TRACE_VALUE(_argc);
			GENERIK_TRACE_VALUE(_argv[0]);
			
			std::string optstr = _list.getoptstr();
			GENERIK_TRACE_VALUE(optstr);
			int opt = -1;
			const char* os = optstr.c_str();
			while ( (opt = getopt(_argc, _argv, os)) != -1 )
			{
				//std::cout<<"found option: "<<(char)opt<<std::endl;
				if (opt == 'h')
				{
					process_help();
				}
				else
				if (opt == ':')
				{
					process_missing(optopt);
			}
				else
				if (opt == '?')
			{
					process_unknown(optopt);
			}
				else
			{
					process_argument(opt);
				}
			}
			
			const char* narg = nextarg();
			
			unsigned int offset = 0;
			arg* found = _list.find(0, offset);
			while (found)
			{
				process_extra_arg(found, narg);
				found = _list.find(0, offset);
				narg = nextarg();
			}
			
			while (narg)
			{
				process_extra_arg(narg);
				narg = nextarg();
			}
			
			process_post();
		}

		/** Post process method.
		 This method can be implemented to do a post processing the arguments.
		 */
		virtual bool post()
		{
			return true;
		}
		
		/** Extracts remaining strings.
		 This method returns each remaining string after argument parsing has finished.
		 It returns NULL when no more arguments are left.
		 */
		const char* nextarg()
		{
			const char* val = _argv[optind];
			if (optind++ < _argc) return val;
			return 0;
		}

	};

}

#endif



